package top.milkbox.log.modular.main.service.impl;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentInfo;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.milkbox.common.annotation.CommonLog;
import top.milkbox.common.enums.CommonSortTypeEnum;
import top.milkbox.common.enums.LogCategoryEnum;
import top.milkbox.common.exceprion.CommonServiceException;
import top.milkbox.log.modular.main.entity.LogMainEntity;
import top.milkbox.log.modular.main.mapper.LogMainMapper;
import top.milkbox.log.modular.main.param.LogMainAddParam;
import top.milkbox.log.modular.main.param.LogMainEditParam;
import top.milkbox.log.modular.main.param.LogMainIdParam;
import top.milkbox.log.modular.main.param.LogMainPageParam;
import top.milkbox.log.modular.main.service.LogMainService;
import top.milkbox.log.modular.main.vo.LogMainVo;
import top.milkbox.common.utils.CommonUtil;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * 日志_日志主表（log_main）服务层实现类
 *
 * @author milkbox
 * @date 2024-1-18
 */
@Slf4j
@Service
public class LogMainServiceImpl extends ServiceImpl<LogMainMapper, LogMainEntity> implements LogMainService {

    private LogMainMapper logMainMapper;

    public LogMainServiceImpl(LogMainMapper logMainMapper) {
        this.logMainMapper = logMainMapper;
    }

    @Resource(name = "logExecutorService")
    private ExecutorService logExecute;

    @Override
    public void add(LogMainAddParam addParam) {
        LogMainEntity entity = BeanUtil.toBean(addParam, LogMainEntity.class);
        super.save(entity);
    }

    @Override
    public void delete(List<LogMainIdParam> paramList) {
        super.removeByIds(paramList.stream().map(LogMainIdParam::getId).toList());
    }

//    @Override
//    public void edit(LogMainEditParam editParam) {
//        findEntity(editParam.getId());
//        LogMainEntity entity = BeanUtil.toBean(editParam, LogMainEntity.class);
//        super.updateById(entity);
//    }

    @Override
    public LogMainVo detail(LogMainIdParam idParam) {
        LogMainEntity entity = findEntity(idParam.getId());
        LogMainVo vo = BeanUtil.toBean(entity, LogMainVo.class);
        // 此处进行数据翻译操作，，根据不同的业务逻辑将entity对象转为vo对象......

        return vo;
    }

    @Override
    public LogMainEntity findEntity(Integer entityId) {
        LogMainEntity entity = super.getById(entityId);
        if (ObjectUtil.isEmpty(entity)) {
            throw new CommonServiceException("实体未找到（{}）", entityId);
        }
        return entity;
    }

    @Override
    public Page<LogMainVo> page(LogMainPageParam pageParam) {
        QueryWrapper<LogMainEntity> queryWrapper = new QueryWrapper<>();
        if (ObjectUtil.isAllNotEmpty(pageParam.getSortField(), pageParam.getSortType())) {
            queryWrapper.orderBy(true,
                    pageParam.getSortType() == CommonSortTypeEnum.ASC,
                    StrUtil.toUnderlineCase(pageParam.getSortField()));
        } else {
            queryWrapper.lambda().orderByAsc(LogMainEntity::getSortCode);
        }
        queryWrapper.lambda().orderByAsc(LogMainEntity::getId);

        Page<LogMainEntity> entityPage = super.page(pageParam.toBaomidouPage(), queryWrapper);
        // 此处进行远程调用或关联查询......

        Page<LogMainVo> voPage = CommonUtil.convertPage(entityPage, entity -> {
            LogMainVo vo = BeanUtil.toBean(entity, LogMainVo.class);
            // 此处进行数据翻译操作，根据不同的业务逻辑将entity对象转为vo对象......

            return vo;
        });
        return voPage;
    }

    @Override
    public void saveNormalLogAsync(ProceedingJoinPoint joinPoint, Object result) {
        LogMainAddParam addParam = createLogMainAddParam();
        logExecute.execute(() -> {
            LogMainAddParam finaAddParam =
                    processLogMainAddParam(addParam, joinPoint, result, null);
            add(finaAddParam); // 保存到数据库
        });
    }

    @Override
    public void saveThrowableLogAsync(ProceedingJoinPoint joinPoint, Throwable throwable) {
        LogMainAddParam addParam = createLogMainAddParam();
        logExecute.execute(() -> {
            LogMainAddParam finaAddParam =
                    processLogMainAddParam(addParam, joinPoint, null, throwable);
            add(finaAddParam); // 保存到数据库
        });
    }

    /**
     * @return 返回当前登录用户的id，如果未登录则返回空
     */
    private Integer getLoginId() {
        try {
            return StpUtil.getLoginIdAsInt();
        } catch (NotLoginException e) {
            return null;
        }
    }

    /**
     * 创建并处理日志对象
     * 由于需要处理请求体，所以这里的处理是必须在当前请求线程中执行
     *
     * @return 返回日志对象
     */
    private LogMainAddParam createLogMainAddParam() {
        LogMainAddParam addParam = new LogMainAddParam(); // 创建被保存的日志参数对象

        // 当前保存日志的用户id
        addParam.setCreateUser(getLoginId());

        // 如果有request对象则保存相关信息，如果没有则日志为method类型
        ServletRequestAttributes servletRequestAttributes
                = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ObjectUtil.isNotNull(servletRequestAttributes)) {
            addParam.setCategory(LogCategoryEnum.API);

            HttpServletRequest request = servletRequestAttributes.getRequest();
            addParam.setRequestUrl(request.getRequestURL().toString());
            // addParam.setOperationIp(NetUtil.getRealIp(request)); // 自己的获取ip方式
            addParam.setOperationIp(JakartaServletUtil.getClientIP(request)); // 糊涂工具的获取ip方式

            String userAgentString = JakartaServletUtil.getHeaderIgnoreCase(
                    request, "User-Agent");
            UserAgent userAgent = UserAgentUtil.parse(userAgentString);
            if (userAgent.getBrowser().getName().equals(UserAgentInfo.NameUnknown)) {
                addParam.setOperationBrowser(userAgentString);
            } else {
                addParam.setOperationBrowser(userAgent.getBrowser().getName());
                addParam.setOperationSystem(userAgent.getPlatform().getName());
            }
        } else {
            addParam.setCategory(LogCategoryEnum.METHOD);
        }

        return addParam;
    }

    /**
     * 在子线程中继续处理日志对象
     *
     * @param joinPoint 切点
     * @param result    方法的返回值
     * @param throwable 抛出的异常，如果为空则表示没有发生异常，可以通过这个值判断方法的运行状况
     * @return 返回AddParam对象
     */
    private LogMainAddParam processLogMainAddParam(
            LogMainAddParam addParam, ProceedingJoinPoint joinPoint, Object result, Throwable throwable) {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CommonLog commonLog = method.getAnnotation(CommonLog.class);

        // 状态信息
        addParam.setStatus(throwable == null ? 1 : 0);

        // 注解上的信息
        addParam.setName(commonLog.value());
        addParam.setLevel(commonLog.level());
        if (throwable != null) {
            String errorStackJson = JSONUtil.toJsonPrettyStr(throwable.getStackTrace());
            if (ObjectUtil.isNotEmpty(commonLog.description())) {
                addParam.setDescription(commonLog.description() + "\n错误信息：\n" + errorStackJson);
            } else {
                addParam.setDescription("错误信息：\n" + errorStackJson);
            }
        } else {
            addParam.setDescription(commonLog.description());
        }
        addParam.setModule(commonLog.module());
        addParam.setType(commonLog.type());

        // 在允许的情况下，保存入参和返回值
        Object[] args = joinPoint.getArgs();
        if (ObjectUtil.isNotEmpty(args) && commonLog.saveParam()) {
            addParam.setParamJson(args);
        }
        if (ObjectUtil.isNotNull(result) && commonLog.saveResult()) {
            addParam.setResultJson(result);
        }

        return addParam;
    }

}